﻿#region Usings

using System;
using System.Windows;
using System.Windows.Media;
using Box2DX.Common;

#endregion

namespace SilverBox.Physics
{
    public sealed class PhysicsContext
    {
        public static readonly DependencyProperty PhysicsContextProperty =
            DependencyProperty.RegisterAttached("PhysicsContext", typeof (PhysicsContext), typeof (FrameworkElement),
                                                new PropertyMetadata(null, HostChanged));

        private FrameworkElement host;
        private Simulator simulator;

        public PhysicsContext()
        {
            // default values
            WorldSize = 25;
            Gravity = 10;
            Interval = 16.666;
            ShouldStartImmediately = true;
        }

        public Simulator Simulator
        {
            get { return simulator; }
            private set { simulator = value; }
        }

        public double WorldSize { get; set; }

        public double Gravity { get; set; }

        public double Interval { get; set; }

        public bool ShouldStartImmediately { get; set; }

        private bool IsPendingHostLoad { get; set; }

        public FrameworkElement Host
        {
            get { return host; }
            set
            {
                if (host != value)
                {
                    FrameworkElement oldHost = Host;
                    FrameworkElement newHost = value;

                    if (oldHost != null)
                    {
                        UnhookLoaded();
                        IsPendingHostLoad = true;
                        if (Simulator != null)
                        {
                            Stop();
                            Simulator = null;
                        }
                    }

                    host = value;

                    if (newHost != null)
                    {
                        if (newHost.ActualHeight == 0 && newHost.ActualWidth == 0)
                            newHost.SizeChanged += SizeChanged;
                        else
                            InitializeSimulator();
                    }
                }
            }
        }


        private void Start()
        {
            System.Diagnostics.Debug.Assert(!Simulator.IsRunning);
            if (Simulator != null)
            {
                Simulator.Start();
            }
        }

        private void Stop()
        {
            System.Diagnostics.Debug.Assert(Simulator.IsRunning);
            if (Simulator != null)
            {
                Simulator.Stop();
            }
        }

        private void InitializeSimulator()
        {
            var screenSize = new Vector2((float) Host.ActualWidth, (float) Host.ActualHeight);
            float ratio = screenSize.X/screenSize.Y;
            float halfWorldSize = (float) WorldSize/2.0f;
            var extent = new Vector2(-ratio*halfWorldSize, halfWorldSize);

            Simulator = new Simulator(
                this,
                new Vector2(0, 0),
                screenSize,
                extent,
                -extent,
                (float) Gravity,
                (float) Interval);
            if (ShouldStartImmediately)
            {
                Host.Dispatcher.BeginInvoke(delegate { Start(); });
            }
        }

        private void HookLoaded()
        {
            if (!IsPendingHostLoad)
            {
                IsPendingHostLoad = true;
                Host.Loaded += new RoutedEventHandler(OnHostLoaded);
            }
        }

        private void UnhookLoaded()
        {
            if (IsPendingHostLoad)
            {
                IsPendingHostLoad = false;
                Host.Loaded -= new RoutedEventHandler(OnHostLoaded);
            }
        }

        private void SizeChanged(object sender, EventArgs e)
        {
            if (Host.ActualWidth != 0 && Host.ActualHeight != 0)
            {
                Host.SizeChanged -= SizeChanged;

                InitializeSimulator();
            }
        }

        private void OnHostLoaded(object sender, RoutedEventArgs e)
        {
            InitializeSimulator();
        }

        public static PhysicsContext GetPhysicsContext(DependencyObject target)
        {
            return (PhysicsContext) target.GetValue(PhysicsContextProperty);
        }

        public static void SetPhysicsContext(DependencyObject target, PhysicsContext value)
        {
            target.SetValue(PhysicsContextProperty, value);
        }

        private static void HostChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
        {
            var newContext = (PhysicsContext) args.NewValue;
            var oldContext = (PhysicsContext) args.OldValue;

            if (newContext != null)
                newContext.Host = target as FrameworkElement;
            if (oldContext != null)
                oldContext.Host = null;
        }

        public static PhysicsContext FindContext(FrameworkElement element)
        {
            while (element != null)
            {
                PhysicsContext context = GetPhysicsContext(element);
                if (context != null)
                    return context;
                element = VisualTreeHelper.GetParent(element) as FrameworkElement;
            }

            return null;
        }
    }
}